package com.ocm.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.FileCopyUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import com.ocm.controller.mapper.JsonMapping;
import com.ocm.controller.model.ClassInstance;
import com.ocm.controller.model.SecuritySearchForm;
import com.ocm.controller.util.OCMUtil;
import com.ocm.core.constant.OCMConstant;
import com.ocm.core.domain.AccessMask;
import com.ocm.core.domain.Permission;
import com.ocm.core.dto.ClassDefinitionDto;
import com.ocm.core.dto.ClassInstanceDto;
import com.ocm.core.dto.GroupDto;
import com.ocm.core.dto.PropertyDefinitionDto;
import com.ocm.core.dto.PropertyValueDto;
import com.ocm.core.dto.SecurityDto;
import com.ocm.core.dto.UserInfoDto;
import com.ocm.core.exception.OCMRuntimeException;
import com.ocm.core.mapping.DTOConfiguration;
import com.ocm.core.mapping.IDMapping;
import com.ocm.core.model.Files;
import com.ocm.core.service.ClassDefService;
import com.ocm.core.service.ClassInstService;
import com.ocm.core.service.SecurityService;

@Controller
public class ClassInstController {

   public static final String CREATE_PAGE = "classInst/create";
   public static final String EDIT_PAGE = "classInst/edit";
   public static final String MODIFY_PAGE = "classInst/modify";
   public static final String CHECKIN_PAGE = "classInst/checkin";
   public static final String HISTORIES_PAGE = "classInst/histories";
   public static final String LIST_SECURITY_PAGE = "classInst/security/list";
   public static final String LIST_SECURITY_SYNC_PAGE = "classInst/security/sync";
   public static final String SEARCH_SECURITY_PAGE = "classInst/security/search";
   public static final String EDIT_SECURITY_PAGE = "classInst/security/edit";
   public static final String STATUS_PAGE = "classInst/status";
   public static final String STATUSHTML_PAGE = "classInst/statusHTML";

   private ClassDefService classDefService;

   private ClassInstService classInstService;

   private SecurityService securityService;

   private JsonMapping jsonMapping;

   @Autowired
   public ClassInstController(
         @Qualifier("classDefService") ClassDefService classDefService,
         @Qualifier("securityService") SecurityService securityService,
         @Qualifier("jsonMapping") JsonMapping jsonMapping,
         @Qualifier("classInstService") ClassInstService classInstService) {
      this.classDefService = classDefService;
      this.classInstService = classInstService;
      this.securityService = securityService;
      this.jsonMapping = jsonMapping;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/viewCreateInst", method = RequestMethod.GET)
   public String viewActionPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);

      List<PropertyDefinitionDto> propertyDefs = this.classDefService.getAllProperties(classDefId);

      model.addAttribute("propertyDefs", propertyDefs);
      return CREATE_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/edit", method = RequestMethod.GET)
   public String viewEditInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);

      ClassInstanceDto classInst = this.classInstService.findClassInst(classDefId, 
            DTOConfiguration.instance(IDMapping.INST_PROPERTY_EXCLUDED_DATA));

      List<PropertyValueDto> propertyValues = classInst.getProperties();
      model.addAttribute("propertyDefs", propertyValues);
      model.addAttribute("hasSyncSecurity", classInst.isHasSyncSecurity());
      return EDIT_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/modify", method = RequestMethod.GET)
   public String viewModifyInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);

      ClassInstanceDto classInst = this.classInstService.findClassInst(classDefId, DTOConfiguration.instance(IDMapping.INST_PROPERTY_EXCLUDED_CONTENTS));
      ClassDefinitionDto classDefinitionDto = classInst.getClassDefinition();
      List<PropertyValueDto> propertyValues = classInst.getProperties();
      model.addAttribute("propertyDefs", propertyValues);
      model.addAttribute("classInstUID", classDefinitionDto.getClassDefUID());
      return MODIFY_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/modify", method = RequestMethod.POST)
   public String modifyInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, @ModelAttribute("classInst")
         ClassInstance classInstanceDto, BindingResult result, ModelMap model) {
      this.classInstService.updateInstance(classDefId, classInstanceDto.getPropertyValues());
      return STATUSHTML_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/checkin", method = RequestMethod.GET)
   public String viewCheckinInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      viewModifyInstPage(classDefId, classDefType, model);
      return CHECKIN_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/history", method = RequestMethod.GET)
   public String viewHistoryInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      List<ClassInstanceDto> classInsts = this.classInstService.getHistory(classDefId);
      model.addAttribute("classInsts", classInsts);
      return HISTORIES_PAGE;
   }
   
   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/list", method = RequestMethod.GET)
   public String viewSecurityInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      ClassInstanceDto classInstanceDtoDto = this.classInstService.findClassInst(classDefId, null);
      List<SecurityDto> existSecurityDtos = classInstanceDtoDto.getClassDefinition().getSecurities();
      Long[] userIds = new Long[existSecurityDtos.size()];
      for (int index=0; index<userIds.length; index++){
         userIds[index] = existSecurityDtos.get(index).getUserId();
      }
      List<UserInfoDto> userInfoDtos = this.securityService.findAll(userIds);
      for (SecurityDto securityDto : existSecurityDtos){
         securityDto.setUserInfo(com.ocm.core.util.OCMUtil.getUserInfoDto(securityDto.getUserId(), userInfoDtos));
      }
      model.put("securities", existSecurityDtos);
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return LIST_SECURITY_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/sync", method = RequestMethod.GET)
   public String viewSyncSecurityInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      ClassInstanceDto classInstanceDtoDto = this.classInstService.findClassInst(classDefId, null);
      List<SecurityDto> existSecurityDtos = classInstanceDtoDto.getClassDefinition().getSecurities();
      Long[] userIds = new Long[existSecurityDtos.size()];
      for (int index=0; index<userIds.length; index++){
         userIds[index] = existSecurityDtos.get(index).getUserId();
      }
      List<UserInfoDto> userInfoDtos = this.securityService.findAll(userIds);
      for (SecurityDto securityDto : existSecurityDtos){
         securityDto.setUserInfo(com.ocm.core.util.OCMUtil.getUserInfoDto(securityDto.getUserId(), userInfoDtos));
      }
      model.put("securities", existSecurityDtos);
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return LIST_SECURITY_SYNC_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/sync", method = RequestMethod.POST)
   public String syncSecurityInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      this.classInstService.syncSecurity(classDefId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/restore", method = RequestMethod.POST)
   public String restoreCreatorSecurity(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      this.classInstService.restoreCreatorSecurity(classDefId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/assign", method = RequestMethod.GET)
   public String viewSecurityPage(@PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return SEARCH_SECURITY_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/assign", method = RequestMethod.POST)
   public String assignSecurityPage(@RequestParam("JSONSecurityIds") String jsonSecurities,
         @PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      Long[] ids = OCMUtil.convertIDsFromJsonString(jsonSecurities);
      List<UserInfoDto> userInfoDtos = this.securityService.findAll(ids);
      List<SecurityDto> securityDtos = OCMUtil.getDefaultSecurities(userInfoDtos);
      this.classInstService.assignSecurity(classDefId, securityDtos);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/search", method = RequestMethod.POST)
   public @ResponseBody JSONArray searchSecurity(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType,
         @ModelAttribute("secSearchform")SecuritySearchForm searchForm, ModelMap model) {
      JSONArray jsonArray = null;
      if (SecuritySearchForm.TYPE.USER.getType().equals(searchForm.getType())){
         List<UserInfoDto> userDtos = this.securityService.searchByUserName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchUsers(userDtos);
      }else if (SecuritySearchForm.TYPE.GROUP.getType().equals(searchForm.getType())){
         List<GroupDto> groupDtos = this.securityService.searchByGroupName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchGroups(groupDtos);
      }else if (SecuritySearchForm.TYPE.BOTH.getType().equals(searchForm.getType())){
         List<UserInfoDto> userDtos = this.securityService.searchByName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchUsers(userDtos);
      }
      return jsonArray;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/{userInfoId}/edit", method = RequestMethod.GET)
   public String viewEditSecurityPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType,@PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      ClassInstanceDto classInstanceDtoDto = this.classInstService.findClassInst(classDefId, null);
      List<SecurityDto> existSecurityDtos = classInstanceDtoDto.getClassDefinition().getSecurities();
      for (SecurityDto securityDto : existSecurityDtos){
         if (securityDto.getUserId() == userInfoId){
            UserInfoDto userInfoDto = this.securityService.findById(userInfoId);
            securityDto.setUserInfo(userInfoDto);
            model.put("security",securityDto);
            Permission[] permissions = Permission.getPermissions(securityDto.getAccessMask());
            model.put("leftData",jsonMapping.convertPermisson(AccessMask.FULLPERMISSION.getRestPermissions(permissions)).toString());
            model.put("rightData",jsonMapping.convertPermisson(permissions).toString());
            break;
         }
      }
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return EDIT_SECURITY_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/{userInfoId}/remove", method = RequestMethod.POST)
   public String removeSecurityPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType,@PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      this.classInstService.removeSecurity(classDefId, userInfoId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/security/{userInfoId}/edit", method = RequestMethod.POST)
   public String editSecurityPage(@RequestParam("JSONPermissions") String jsonPermission,@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, @PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      int[] permissions = OCMUtil.convertFromJsonString(jsonPermission);
      int accessMask=0; 
      for (int permission:permissions){
         accessMask += permission;
      }
      this.classInstService.updatePermission(classDefId, userInfoId, accessMask);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/checkin", method = RequestMethod.POST)
   public String checkinInst(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, @ModelAttribute("classInst")
         ClassInstance classInstanceDto, BindingResult result, ModelMap model) {
      this.classInstService.checkin(classDefId, classInstanceDto.getPropertyValues());
      return STATUSHTML_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/checkout", method = RequestMethod.POST)
   public String checkoutInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      this.classInstService.checkout(classDefId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/delete", method = RequestMethod.POST)
   public String deleteInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      this.classInstService.deleteClassInstance(classDefId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/cancelCheckout", method = RequestMethod.POST)
   public String cancelCheckoutInstPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      this.classInstService.cancelCheckout(classDefId);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/download", method = RequestMethod.GET)
   public String download(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model, HttpServletResponse response) throws IOException {
      Files file = this.classInstService.downloadFile(classDefId);
      if (file.getInputStream() != null){
         response.setContentType(file.getType());
         response.setContentLength(file.getLength());
         response.setHeader("Content-Disposition","attachment; filename=\"" + file.getFilename() +"\"");
         FileCopyUtils.copy(file.getInputStream(), response.getOutputStream());
         return null;
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classInst/{classDefType}/{selectedClassDefId}/createInst", method = RequestMethod.POST)
   public String createInstance(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, @ModelAttribute("classInst")
         ClassInstance classInstanceDto, BindingResult result, ModelMap model) {
      try {
      List<PropertyValueDto> propertiesValue = classInstanceDto.getPropertyValues();
      CommonsMultipartFile fileData = classInstanceDto.getFileData();
      if (fileData != null){
         PropertyValueDto propertyValueDto = OCMUtil.getPropertyValue(OCMConstant.DOCUMENT_TITLE, propertiesValue);
         Object value = propertyValueDto.getValue();
         propertyValueDto.setValue(value == null || value.toString().trim().length() == 0 ? fileData.getOriginalFilename():value);

         propertyValueDto = OCMUtil.getPropertyValue(OCMConstant.CONTENT_TYPE, propertiesValue);
         propertyValueDto.setValue(fileData.getContentType());

         propertyValueDto = OCMUtil.getPropertyValue(OCMConstant.CONTENT_LENGTH, propertiesValue);
         propertyValueDto.setValue(fileData.getSize());
   
         propertyValueDto = OCMUtil.getPropertyValue(OCMConstant.FILE_NAME, propertiesValue);
         propertyValueDto.setValue(fileData.getOriginalFilename());

         propertyValueDto = OCMUtil.getPropertyValue(OCMConstant.CONTENT_DATA, propertiesValue);
         propertyValueDto.setValue(fileData.getInputStream());
      }
      this.classInstService.createInstance(classDefId, propertiesValue);
      }catch (Exception e) {
         throw new OCMRuntimeException (e);
      }
      return STATUSHTML_PAGE;
   }
}
